package com.stars.easyms.schedule.compatibility;

import com.stars.easyms.schedule.bean.DbScheduleSubTask;
import com.stars.easyms.schedule.util.MapUtil;
import com.stars.easyms.schedule.util.ReflectUtil;
import com.stars.easyms.schedule.util.ApplicationContextHolder;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

/**
 * Quartz框架兼容类
 *
 * @author guoguifang
 */
@SuppressWarnings("unchecked")
public class QuartzCompatibility {

    private static Class<?> jobClass;

    private static Class<?> schedulerFactoryBeanClass;

    private static Object schedulerFactoryBean;

    private static Constructor jobExecutionContextConstructor;

    private static Class<?> triggerFiredBundleClass;

    private static Method executeMethod;

    private static Method jobExecutionContextPutMethod;

    private static Object scheduler;

    private static Constructor jobDetailImplConstructor;

    private static Method jobDetailImplSetDescriptionMethod;

    private static String applicationContextSchedulerContextKey;

    static {
        jobClass = ReflectUtil.forNameWithNoException("org.quartz.Job");
        if (jobClass != null) {
            Class<?> jobExecutionContextClass = ReflectUtil.forNameWithNoException("org.quartz.JobExecutionContext");
            Class<?> jobExecutionContextImplClass = ReflectUtil.forNameWithNoException("org.quartz.impl.JobExecutionContextImpl");
            Class<?> schedulerClass = ReflectUtil.forNameWithNoException("org.quartz.Scheduler");
            schedulerFactoryBeanClass = ReflectUtil.forNameWithNoException("org.springframework.scheduling.quartz.SchedulerFactoryBean");
            if (schedulerFactoryBeanClass != null) {
                schedulerFactoryBean = ApplicationContextHolder.getBean(schedulerFactoryBeanClass);
                if (schedulerFactoryBean == null) {
                    schedulerFactoryBean = ReflectUtil.newInstance(schedulerFactoryBeanClass);
                    ReflectUtil.invoke(ReflectUtil.getMethodByName(schedulerFactoryBeanClass, "afterPropertiesSet"), schedulerFactoryBean);
                }
                scheduler = ReflectUtil.invoke(ReflectUtil.getMethodByName(schedulerFactoryBeanClass, "getScheduler"), schedulerFactoryBean);
                if (scheduler != null) {
                    Object schedulerContext = ReflectUtil.invoke(ReflectUtil.getMethodByName(schedulerClass, "getContext"), scheduler);
                    if (schedulerContext instanceof Map) {
                        Map schedulerContextAsMap = (Map) schedulerContext;
                        applicationContextSchedulerContextKey = (String) ReflectUtil.getFieldByName(schedulerFactoryBeanClass, "applicationContextSchedulerContextKey", schedulerFactoryBean);
                        if (applicationContextSchedulerContextKey != null) {
                            schedulerContextAsMap.put(applicationContextSchedulerContextKey, ApplicationContextHolder.getApplicationContext());
                        } else {
                            schedulerContextAsMap.put("applicationContextKey", ApplicationContextHolder.getApplicationContext());
                            schedulerContextAsMap.put("applicationContext", ApplicationContextHolder.getApplicationContext());
                            schedulerContextAsMap.put("application_context", ApplicationContextHolder.getApplicationContext());
                        }
                    }
                }
            }
            triggerFiredBundleClass = ReflectUtil.forNameWithNoException("org.quartz.spi.TriggerFiredBundle");
            jobExecutionContextConstructor = ReflectUtil.getConstructor(jobExecutionContextImplClass, schedulerClass, triggerFiredBundleClass, jobClass);
            executeMethod = ReflectUtil.getMethodByName(jobClass, "execute", jobExecutionContextClass);
            jobExecutionContextPutMethod = ReflectUtil.getMethodByName(jobExecutionContextImplClass, "put", Object.class, Object.class);
            Class<?> jobDetailImplClass = ReflectUtil.forNameWithNoException("org.quartz.impl.JobDetailImpl");
            jobDetailImplConstructor = ReflectUtil.getConstructor(jobDetailImplClass, String.class, Class.class);
            jobDetailImplSetDescriptionMethod = ReflectUtil.getMethodByName(jobDetailImplClass, "setDescription", String.class);
        }
    }

    /**
     * 关闭/打开quartz的自动启动功能
     */
    public static void setAutoStartup(boolean autoStartup) {
        if (schedulerFactoryBean != null) {
            ReflectUtil.invoke(ReflectUtil.getMethodByName(schedulerFactoryBeanClass, "setAutoStartup", boolean.class), schedulerFactoryBean, autoStartup);
        }
    }

    /**
     * 启动quartz框架
     */
    public static void start() {
        if (schedulerFactoryBean != null) {
            ReflectUtil.invoke(ReflectUtil.getMethodByName(schedulerFactoryBeanClass, "start"), schedulerFactoryBean);
        }
    }

    /**
     * 暂停quartz框架
     */
    public static void stop() {
        if (schedulerFactoryBean != null) {
            ReflectUtil.invoke(ReflectUtil.getMethodByName(schedulerFactoryBeanClass, "stop"), schedulerFactoryBean);
        }
    }

    /**
     * 判断参数bean是否是quartz的自动任务
     */
    public static boolean isQuartzJob(Object bean) {
        return jobClass != null && executeMethod != null && jobExecutionContextConstructor != null && bean != null && jobClass.isAssignableFrom(bean.getClass());
    }

    /**
     * 执行quartz的自动任务
     */
    public static Object invoke(Object bean, DbScheduleSubTask executableSubTask) throws InvocationTargetException, IllegalAccessException, InstantiationException {
        return executeMethod.invoke(bean, getJobExecutionContext(bean, executableSubTask));
    }

    private static Object getJobExecutionContext(Object bean, DbScheduleSubTask executableSubTask) throws IllegalAccessException, InvocationTargetException, InstantiationException {
        Object jobDetail = ReflectUtil.newInstance(jobDetailImplConstructor, executableSubTask.getTaskId(), bean.getClass());
        ReflectUtil.invoke(jobDetailImplSetDescriptionMethod, jobDetail, executableSubTask.getTaskName());
        Class<?> cronTriggerImplClass = ReflectUtil.forNameWithNoException("org.quartz.impl.triggers.CronTriggerImpl");
        Object trigger = ReflectUtil.newInstance(cronTriggerImplClass);
        ReflectUtil.invoke(ReflectUtil.getMethodByName(cronTriggerImplClass, "setName", String.class), trigger, executableSubTask.getTaskId());
        ReflectUtil.invoke(ReflectUtil.getMethodByName(cronTriggerImplClass, "setJobName", String.class), trigger, executableSubTask.getTaskId());
        ReflectUtil.invoke(ReflectUtil.getMethodByName(cronTriggerImplClass, "setDescription", String.class), trigger, executableSubTask.getTaskName());
        ReflectUtil.invoke(ReflectUtil.getMethodByName(cronTriggerImplClass, "setNextFireTime", Date.class), trigger, executableSubTask.getNextFireTime());
        if (executableSubTask.getCronExpression() != null) {
            ReflectUtil.invoke(ReflectUtil.getMethodByName(cronTriggerImplClass, "setCronExpression", String.class), trigger, executableSubTask.getCronExpression());
        }
        Object triggerFiredBundle = ReflectUtil.newInstance(triggerFiredBundleClass.getConstructors()[0], jobDetail, trigger, null, false, null, null, null, null);
        Object jobExecutionContext = jobExecutionContextConstructor.newInstance(scheduler, triggerFiredBundle, bean);
        ReflectUtil.invoke(jobExecutionContextPutMethod, jobExecutionContext, "partitionCount", executableSubTask.getPartitionCount());
        ReflectUtil.invoke(jobExecutionContextPutMethod, jobExecutionContext, "partitionIndex", executableSubTask.getPartitionIndex());
        if (applicationContextSchedulerContextKey != null) {
            ReflectUtil.invoke(jobExecutionContextPutMethod, jobExecutionContext, applicationContextSchedulerContextKey, ApplicationContextHolder.getApplicationContext());
        } else {
            ReflectUtil.invoke(jobExecutionContextPutMethod, jobExecutionContext, "applicationContextKey", ApplicationContextHolder.getApplicationContext());
            ReflectUtil.invoke(jobExecutionContextPutMethod, jobExecutionContext, "applicationContext", ApplicationContextHolder.getApplicationContext());
            ReflectUtil.invoke(jobExecutionContextPutMethod, jobExecutionContext, "application_context", ApplicationContextHolder.getApplicationContext());
        }
        Map<String, Object> taskParameterMap = MapUtil.parse(executableSubTask.getParameters());
        if (taskParameterMap != null) {
            for (Map.Entry entry : taskParameterMap.entrySet()) {
                ReflectUtil.invoke(jobExecutionContextPutMethod, jobExecutionContext, entry.getKey(), entry.getValue());
            }
        }
        return jobExecutionContext;
    }

}
